﻿// ===============================================================================
//  Copyright © ymind.net. All rights reserved .
//  官方网站：http://ymind.net/
//  版权所有：彦铭工作室 老陈出品
// ===============================================================================

using System;
using System.Collections.Generic;
using System.Linq;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Builders;
using MongoDB.Driver.Wrappers;

namespace Lcsoft.Data.MongoDB
{
	/// <summary>
	/// 为实体对象基于 MongoDB 的数据访问层提供基类。
	/// </summary>
	/// <typeparam name="T">表示实体对象的类型。</typeparam>
	/// <typeparam name="TId">表示唯一标志符的类型。</typeparam>
	public abstract class MongoDBDALBase<T, TId> : DatabaseDALBase
	{
		private MongoServer _mongoServer;

		/// <summary>
		/// 初始化 <see cref="MongoDBDALBase{T, TId}"/> 对象。
		/// </summary>
		/// <param name="masterGroup">连接字符串。</param>
		protected MongoDBDALBase(string masterGroup) : this(masterGroup, masterGroup) { }

		/// <summary>
		/// 初始化 <see cref="MongoDBDALBase{T, TId}"/> 对象。
		/// </summary>
		/// <param name="masterGroup">主机连接字符串的分组名称。</param>
		/// <param name="slaveGroup">从机连接字符串的分组名称。</param>
		protected MongoDBDALBase(string masterGroup, string slaveGroup) : base("MongoDB", masterGroup, slaveGroup) { }

		/// <summary>
		/// 获取默认数据集名称。
		/// </summary>
		protected abstract string CollectionName { get; }

		/// <summary>
		/// 获取对象标识符名称。
		/// </summary>
		protected virtual string IdName
		{
			get { return "_id"; }
		}

		/// <summary>
		/// 返回数据库服务器。
		/// </summary>
		/// <param name="master">是否返回主机数据库的实例。</param>
		/// <returns>返回 <see cref="MongoServer"/>。</returns>
		protected MongoServer GetMongoServer(bool master = true)
		{
			if (this._mongoServer == null)
			{
				var mongoClient = new MongoClient(master ? this.MasterConnectionStrings : this.SlaveConnectionStrings);

				return this._mongoServer = mongoClient.GetServer();
			}

			return this._mongoServer;
		}

		/// <summary>
		/// 返回数据库的实例。
		/// </summary>
		/// <param name="master">是否返回主机数据库的实例。</param>
		/// <param name="customDatabaseName">自定义数据库名称。</param>
		/// <returns>返回 <see cref="MongoDatabase"/>。</returns>
		protected virtual MongoDatabase GetDatabase(bool master = true, string customDatabaseName = null)
		{
			var databaseName = String.IsNullOrWhiteSpace(customDatabaseName) ? this.DatabaseName : customDatabaseName;
			var server = this.GetMongoServer(master);

			server.Connect();

			return server.GetDatabase(databaseName);
		}

		/// <summary>
		/// 返回数据集的实例。
		/// </summary>
		/// <param name="customCollectionName">自定义集合名称。</param>
		/// <param name="master">是否返回主机数据库的实例。</param>
		/// <param name="customDatabaseName">自定义片键。</param>
		/// <returns>返回 <see cref="MongoCollection"/>。</returns>
		protected virtual MongoCollection GetCollection(string customCollectionName = null, bool master = true, string customDatabaseName = null)
		{
			var database = this.GetDatabase(master, customDatabaseName);
			var collectionName = String.IsNullOrWhiteSpace(customCollectionName) ? this.CollectionName : customCollectionName;

			return database.GetCollection(collectionName);
		}

		/// <summary>
		/// 返回数据集的实例。
		/// </summary>
		/// <param name="customCollectionName">自定义集合名称。</param>
		/// <param name="master">是否返回主机数据库的实例。</param>
		/// <param name="customDatabaseName">自定义片键。</param>
		/// <returns>返回 <see cref="MongoCollection{TObject}"/>。</returns>
		protected virtual MongoCollection<TObject> GetCollection<TObject>(string customCollectionName = null, bool master = true, string customDatabaseName = null)
		{
			var database = this.GetDatabase(master, customDatabaseName);
			var collectionName = String.IsNullOrWhiteSpace(customCollectionName) ? this.CollectionName : customCollectionName;

			var collection = database.GetCollection<TObject>(collectionName);

			if (collection == null) throw new MongoException("获取 MongoDB 集合时发生异常。");

			return collection;
		}

		/// <summary>
		/// 返回数据库中所有项的总数。
		/// </summary>
		/// <returns>返回 <see cref="System.Int64"/>。</returns>
		public long GetTotalCount()
		{
			var collection = this.GetCollection();

			return collection.Count();
		}

		/// <summary>
		/// 返回对象标识符的值。
		/// </summary>
		/// <param name="item"><typeparamref name="T"/> 对象。</param>
		/// <returns>返回 <see cref="BsonValue"/>。</returns>
		protected virtual BsonValue GetIdValue(T item) { return BsonValue.Create(((dynamic)item).Id); }

		/// <summary>
		/// 返回一个可用的对象唯一标识符。
		/// </summary>
		/// <returns><typeparamref name="TId"/>，一个可用的对象唯一标识符。</returns>
		/// <remarks>注意：请使用 <see cref="GetDatabase"/> 连接服务器。</remarks>
		public TId GetNewId() { return this.GetNewId(this.GetDatabase()); }

		/// <summary>
		/// 返回新的唯一标识符。
		/// </summary>
		/// <param name="database">保存唯一标识符的数据库对象。</param>
		/// <returns>返回表示唯一标识符的 <see cref="BsonValue"/>。</returns>
		/// <remarks>注意：请使用 <see cref="GetDatabase"/> 连接服务器。</remarks>
		protected abstract TId GetNewId(MongoDatabase database);

		/// <summary>
		/// 返回所有 <typeparamref name="T"/> 对象的集合。
		/// </summary>
		/// <remarks>注意：请使用 <see cref="GetDatabase"/> 连接服务器。</remarks>
		public IEnumerable<T> GetAllItems()
		{
			var collection = this.GetCollection<T>();

			return collection.FindAll();
		}

		/// <summary>
		/// 返回具有指定唯一标识符的 <typeparamref name="T"/> 对象。
		/// </summary>
		/// <param name="id">要返回的 <typeparamref name="T"/> 对象的唯一标识符。</param>
		/// <remarks>注意：请使用 <see cref="GetDatabase"/> 连接服务器。</remarks>
		public T GetItemById(TId id)
		{
			if (Equals(id, default(TId))) throw new ArgumentOutOfRangeException("id");

			var collection = this.GetCollection<T>();

			return collection.FindOne(Query.EQ(this.IdName, BsonValue.Create(id)));
		}

		/// <summary>
		/// 向数据库添加一个 <typeparamref name="T"/> 对象。
		/// </summary>
		/// <param name="item">要添加的 <typeparamref name="T"/> 对象。</param>
		/// <param name="writeConcern">指定 <see cref="WriteConcern"/> 对象。</param>
		/// <remarks>注意：请使用 <see cref="GetDatabase"/> 连接服务器。</remarks>
		public WriteConcernResult AddNew(T item, WriteConcern writeConcern = null)
		{
			var collection = this.GetCollection<T>();

			return collection.Insert(item, writeConcern);
		}

		/// <summary>
		/// 向数据库添加一个 <typeparamref name="T"/> 对象的集合。
		/// </summary>
		/// <param name="items">要添加的 <typeparamref name="T"/> 对象的集合。</param>
		/// <param name="writeConcern">指定 <see cref="WriteConcern"/> 对象。</param>
		/// <remarks>注意：请使用 <see cref="GetDatabase"/> 连接服务器。</remarks>
		public IEnumerable<WriteConcernResult> AddNew(IEnumerable<T> items, WriteConcern writeConcern = null)
		{
			if (items == null) throw new ArgumentNullException("items");

			var collection = this.GetCollection();
			var options = new MongoInsertOptions{
				Flags = InsertFlags.ContinueOnError,
				WriteConcern = writeConcern,
				CheckElementNames = false
			};

			return collection.InsertBatch(items, options);
		}

		/// <summary>
		/// 从数据库删除所有对象。
		/// </summary>
		/// <param name="writeConcern">指定 <see cref="WriteConcern"/> 对象。</param>
		/// <remarks>注意：请使用 <see cref="GetDatabase"/> 连接服务器。</remarks>
		public WriteConcernResult RemoveAll(WriteConcern writeConcern = null)
		{
			var collection = this.GetCollection();

			return collection.RemoveAll(writeConcern);
		}

		/// <summary>
		/// 从数据库删除具有指定唯一标识符的对象。
		/// </summary>
		/// <param name="id">要删除的对象的唯一标识符。</param>
		/// <param name="writeConcern">指定 <see cref="WriteConcern"/> 对象。</param>
		/// <remarks>注意：请使用 <see cref="GetDatabase"/> 连接服务器。</remarks>
		public WriteConcernResult Remove(TId id, WriteConcern writeConcern = null)
		{
			var collection = this.GetCollection();

			return collection.Remove(Query.EQ(this.IdName, BsonValue.Create(id)), writeConcern);
		}

		/// <summary>
		/// 从数据库删除指定的 <typeparamref name="T"/> 对象的集合。
		/// </summary>
		/// <param name="items">要删除的 <typeparamref name="T"/> 对象的集合。</param>
		/// <param name="writeConcern">指定 <see cref="WriteConcern"/> 对象。</param>
		/// <remarks>注意：请使用 <see cref="GetDatabase"/> 连接服务器。</remarks>
		public WriteConcernResult Remove(IEnumerable<T> items, WriteConcern writeConcern = null)
		{
			if (items == null) throw new ArgumentNullException("items");

			var collection = this.GetCollection();

			var query = Query.In(this.IdName, new BsonArray(items.Select(this.GetIdValue)));

			return collection.Remove(query, writeConcern);
		}

		/// <summary>
		/// 更新数据库中的一个 <typeparamref name="T"/> 对象。
		/// </summary>
		/// <param name="item">要更新的 <typeparamref name="T"/> 对象。</param>
		/// <param name="writeConcern">指定 <see cref="WriteConcern"/> 对象。</param>
		/// <remarks>注意：请使用 <see cref="GetDatabase"/> 连接服务器。</remarks>
		public WriteConcernResult UpdateItem(T item, WriteConcern writeConcern = null)
		{
			var collection = this.GetCollection();

			return collection.Update(Query.EQ(this.IdName, this.GetIdValue(item)), UpdateWrapper.Create(item), writeConcern);
		}

		/// <summary>
		/// 更新数据库中的多个 <typeparamref name="T"/> 对象。
		/// </summary>
		/// <param name="items">要更新的 <typeparamref name="T"/> 对象的集合。</param>
		/// <param name="writeConcern">指定 <see cref="WriteConcern"/> 对象。</param>
		/// <remarks>注意：请使用 <see cref="GetDatabase"/> 连接服务器。</remarks>
		public void UpdateItem(IEnumerable<T> items, WriteConcern writeConcern = null) { }

		/// <summary>
		/// 更新或插入数据库中的一个 <typeparamref name="T"/> 对象。
		/// </summary>
		/// <param name="item">要更新的 <typeparamref name="T"/> 对象。</param>
		/// <param name="writeConcern">指定 <see cref="WriteConcern"/> 对象。</param>
		/// <remarks>注意：请使用 <see cref="GetDatabase"/> 连接服务器。</remarks>
		public WriteConcernResult Save(T item, WriteConcern writeConcern = null)
		{
			var collection = this.GetCollection();

			return collection.Save(item, new MongoInsertOptions{
				Flags = InsertFlags.ContinueOnError,
				WriteConcern = writeConcern,
			});
		}

		/// <summary>
		/// 更新或插入数据库中的多个 <typeparamref name="T"/> 对象。
		/// </summary>
		/// <param name="items">要更新的 <typeparamref name="T"/> 对象的集合。</param>
		/// <param name="writeConcern">指定 <see cref="WriteConcern"/> 对象。</param>
		/// <remarks>注意：请使用 <see cref="GetDatabase"/> 连接服务器。</remarks>
		public void Save(IEnumerable<T> items, WriteConcern writeConcern = null)
		{
			if (items == null) throw new ArgumentNullException("items");

			var collection = this.GetCollection();

			foreach (var item in items)
			{
				collection.Save(item, new MongoInsertOptions{
					Flags = InsertFlags.ContinueOnError,
					WriteConcern = writeConcern,
				});
			}
		}

		/// <summary>
		/// 重建所有索引。
		/// </summary>
		public abstract void RebuildIndex();

		/// <summary>
		/// 删除所有索引。
		/// </summary>
		public CommandResult DropAllIndexes()
		{
			var collection = this.GetCollection();

			return collection.DropAllIndexes();
		}

		/// <summary>
		/// 删除具有指定名称的索引。
		/// </summary>
		/// <param name="indexName">要将其删除的索引的名称。</param>
		public CommandResult DropIndexByName(string indexName)
		{
			if (indexName == null) throw new ArgumentNullException("indexName");
			if (String.IsNullOrWhiteSpace(indexName)) throw new ArgumentOutOfRangeException("indexName");

			var collection = this.GetCollection();

			return collection.DropIndexByName(indexName);
		}
	}
}
